#include "GameBoy.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <conio.h>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
u8 SingletonReadMemory( u16 Address ) {
    return GameBoy::GetSingleton()->ReadMemory( Address );
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SingletonWriteMemory( u16 Address, u8 Data ) {
    GameBoy::GetSingleton()->WriteMemory( Address, Data );
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GameBoy* GameBoy::m_Instance = 0;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GameBoy::GameBoy() {
    m_ROM = ( u8* )malloc( sizeof(u8) * ROM_SIZE );
    m_Bank = ( u8* )malloc( sizeof(u8) * BANK_SIZE );
    m_Port = ( u8* )malloc( sizeof(u8) * PORTS_SIZE );
    memset( m_ROM, 0, sizeof(u8) * ROM_SIZE );
    memset( m_Bank, 0, sizeof(u8) * BANK_SIZE );
    memset( m_Port, 0, sizeof(u8) * PORTS_SIZE );
    m_Cart = new Cartridge();
    m_Z80 = new Z80();
    m_Z80->m_Read = &SingletonReadMemory;
    m_Z80->m_Write = &SingletonWriteMemory;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GameBoy::~GameBoy() {
    if( m_Cart ) delete m_Cart;
    if( m_Z80 ) delete m_Z80;
    if( m_ROM ) free( m_ROM );
    if( m_Bank ) free( m_Bank );
    if( m_Port ) free( m_Port );
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GameBoy* GameBoy::GetSingleton() {
    if( !m_Instance ) m_Instance = new GameBoy();
    
    return m_Instance;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool GameBoy::LoadFile( const char* FilePath )const {
    if( !m_Cart->LoadFile(FilePath, m_ROM, m_Bank) ) return false;
    
    return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameBoy::LaunchEmulation() {
    Reset();
    m_Z80->Reset();
    int cycles = 0;
    //for( int i = 0; i < 100000; i++ ) {
    for( ; ; ) {
        // FIXME
        if( kbhit() && (getch() == 27) ) break;
        
        cycles = m_Z80->Execute( m_Z80->Fetch() );
        UpdateGraphics( cycles );
        HandleInterrupts();
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameBoy::UpdateGraphics( int Cycles ) {
    u8 lcdc = ReadMemory( PORT_LCDC );
    u8 stat = ReadMemory( PORT_STAT );
    u8 ly = ReadMemory( PORT_LY );
    
    printf( "  SCL:%3i  LY:%3i", m_ScanlineCycles, m_ROM[0x44] );
    
    // LCD detenido
    if( !TestBit(lcdc, 7) ) {
        m_ScanlineCycles = SCANLINE_CYCLES;
        m_Port[0x44] = 0;
        stat &= 0xFC;
        stat |= 1;
        WriteMemory( PORT_STAT, stat );
    
    // LCD operativo
    } else {
        u8 actual_mode = stat & 0x03;
        u8 new_mode = 0;
        bool need_int = false;
        
        // entrando en vblank
        if( ly >= MAX_ACTIVE_SCANLINE ) {
            // mode 1 (4560 ciclos)
            new_mode = 1;
            stat &= 0xFC;
            stat |= 1;
            need_int = TestBit( stat, 4 );
            
        // aun no es vblank
        } else {
            int mode_2_limit = SCANLINE_CYCLES - MODE_2_CYCLES;
            int mode_3_limit = mode_2_limit - MODE_3_CYCLES;
            
            // mode 2 (80 ciclos)
            if( m_ScanlineCycles >= mode_2_limit ) {
                new_mode = 2;
                stat &= 0xFC;
                stat |= 2;
                need_int = TestBit( stat, 5 );
                
            // mode 3 (172 ciclos)
            } else if( m_ScanlineCycles >= mode_3_limit ) {
                new_mode = 3;
                stat &= 0xFC;
                stat |= 3;
            
            // mode 0 (204 ciclos)
            } else {
                stat &= 0xFC;
                need_int = TestBit( stat, 3 );
            }
        }
        
        // seteo las interrupciones en el flag de interrupciones
        if( (actual_mode != new_mode) && need_int ) WriteMemory( PORT_IF, ReadMemory(PORT_IF) | 2 );
        
        // chequeo el flag de coincidencia y seteo la interrupcion en el flag de interrupciones
        if( ly == ReadMemory(PORT_LYC) ) {
            stat |= 4;
            if( TestBit(stat, 6) ) WriteMemory( PORT_IF, ReadMemory(PORT_IF) | 2 );
        } else stat &= 0xFB;
        
        WriteMemory( PORT_STAT, stat );
        m_ScanlineCycles -= Cycles;
    }
    
    // pongo a 0 el PORT_LY para indicar el inicio de un nuevo el frame
    if( ly > MAX_VBLANK_SCANLINE ) m_Port[0x44] = 0;
    
    // el contador de ciclos de scanline llego a 0, por lo tanto se llego al fin del scanline
    if( m_ScanlineCycles <= 0 ) {
        // reseteo el contador de scanlines y actualizo el PORT_LY
        m_Port[0x44]++;
        m_ScanlineCycles = SCANLINE_CYCLES;
    
        // si el LCD esta detenido no se renderiza nada
        if( TestBit(lcdc, 7) ) return;
    
        // obtengo el numero actual de scanline
        u8 scanline = ReadMemory( PORT_LY );
        
        // si el scanline actual es activo, entonces lo dibujamos
        if( scanline < MAX_ACTIVE_SCANLINE ) { /* FIXME: render scanline */ }
        
        // si se llego al scanline 144, indico que estoy en vblank
        else if( scanline == MAX_ACTIVE_SCANLINE ) WriteMemory( PORT_IF, ReadMemory(PORT_IF) | 1 );
        
        // si se supero el scanline 153 reseteo el PORT_LY (creo que este paso ya se hizo mas arriba igualmente...)
        else if( scanline > MAX_VBLANK_SCANLINE ) {
            m_Port[0x44] = 0;
            printf("  ADENTROOOOOO!"); getch();
        }
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameBoy::HandleInterrupts() {
    u8 if_flag = ReadMemory( PORT_IF );
    
    // del documento de Marat Fayzullin: "HALT is interrupted even when interrupts are disabled"
    if( if_flag > 0 ) {
        if( m_Z80->GetHalted() ) m_Z80->HaltedStatus( false );
        if( m_Z80->GetIME() ) {
            // FIXME: el flag de interrupciones habilitadas se deberia testear aca o adentro del bucle for???
            u8 ie_flag = ReadMemory( PORT_IE );
            printf( "  IF:%2X  IE:%2X", if_flag, ie_flag ); //getch();
            for( int i = 0; i < 8; i++ ) {
                if( TestBit(ie_flag, i) ) RequestInterrupt( i );
            }
        }
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameBoy::RequestInterrupt( u8 Data ) {
    m_Z80->SetIME( false );
    m_Z80->PushWord( m_Z80->GetPC() );
    u8 if_flag = ReadMemory( PORT_IF );
    if_flag &= ~Data;
    WriteMemory( PORT_IF, if_flag );

    switch( Data ) {
        case INTERRUPT_VBLANK: m_Z80->SetPC( 0x0040 ); printf("V-BLANK INT"); break;
        case INTERRUPT_LCDC: m_Z80->SetPC( 0x0048 ); printf("LCDC INT"); break;
        case INTERRUPT_TIMER: m_Z80->SetPC( 0x0050 ); printf("TIMER INT"); break;
        case INTERRUPT_SERIAL: m_Z80->SetPC( 0x0058 ); printf("SERIAL INT"); break;
        case INTERRUPT_P10P13: m_Z80->SetPC( 0x0060 ); printf("P10P13 INT"); break;
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameBoy::Reset() {
    m_ScanlineCycles = SCANLINE_CYCLES;
    m_Port[0x00] = 0xFF;
    m_Port[0x05] = 0x00;
    m_Port[0x06] = 0x00;
    m_Port[0x07] = 0x00;
    m_Port[0x10] = 0x80;
    m_Port[0x11] = 0xBF;
    m_Port[0x12] = 0xF3;
    m_Port[0x14] = 0xBF;
    m_Port[0x16] = 0x3F;
    m_Port[0x17] = 0x00;
    m_Port[0x19] = 0xBF;
    m_Port[0x1A] = 0x7F;
    m_Port[0x1B] = 0xFF;
    m_Port[0x1C] = 0x9F;
    m_Port[0x1E] = 0xBF;
    m_Port[0x20] = 0xFF;
    m_Port[0x21] = 0x00;
    m_Port[0x22] = 0x00;
    m_Port[0x23] = 0xBF;
    m_Port[0x24] = 0x77;
    m_Port[0x25] = 0xF3;
    m_Port[0x26] = 0xF1;
    m_Port[0x40] = 0x91;
    m_Port[0x42] = 0x00;
    m_Port[0x43] = 0x00;
    m_Port[0x45] = 0x00;
    m_Port[0x47] = 0xFC;
    m_Port[0x48] = 0xFF;
    m_Port[0x49] = 0xFF;
    m_Port[0x4A] = 0x00;
    m_Port[0x4B] = 0x00;
    m_Port[0xFF] = 0x00;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
u8 GameBoy::ReadMemory( u16 Address )const {
    // 16KB ROM Bank 01..NN (in cartridge, switchable bank number)
    if( (Address >= 0x4000) && (Address < 0x8000) ) return m_Bank[Address];
    
    // 8KB External RAM (in cartridge, switchable bank, if any)
    else if( (Address >= 0xA000) && (Address < 0xC000) ) { /* FIXME: ??? */ }
    
    // I/O Ports
    else if( (Address >= 0xFF00) && (Address <= 0xFFFF) ) return m_Port[Address & 0x00FF];
        
    // ROM
    else return m_ROM[Address];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameBoy::WriteMemory( u16 Address, u8 Data ) {
    // IO ports
    if( (Address >= 0xFF00) && (Address <= 0xFFFF) ) {
        switch( Address ) {
            //case PORT_LY: m_Port[0x44] = 0; break;
            default: m_Port[Address & 0x00FF] = Data; break;
        }
    }
        
    // ROM
    else m_ROM[Address] = Data;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
